import React, {Component} from 'react';
import {spring, TransitionMotion, presets} from 'react-motion';
import Style from './style/Snake.css';

class Snake extends Component {
    constructor(props) {
        super(props);
        //获取屏幕尺寸，用来创建零食所在的位置
        this.maxX = window.innerWidth;
        this.maxY = window.innerHeight;

        this.colors = ['#EE30A7', '#76EE00', '#00BFFF', '#191970'];
        this.state = {
            left: 0, top: 0,
            snack: {x: 0, y: 0, color: '#76EE00'},
            snakeNodes: [{left: this.maxX / 2, top: this.maxY / 2, color: '#EE30A7'}],
            score: 0
        }
    }

    componentDidMount() {
        this.createSnack();
    }

    /**
     * 检测碰撞，即蛇有没有与零食相碰撞
     * 原理:计算蛇头中心与零食中心的距离x，如果x小于等于50(即两个半径的长度)，说明发生了碰撞；否则未发生碰撞
     */
    collisionChecking = () => {
        const snakeHeadX = this.state.left;
        const snakeHeadY = this.state.top;

        const snackHeadX = this.state.snack.x;
        const snackHeadY = this.state.snack.y;

        const xdiff = snakeHeadX - snackHeadX;
        const ydiff = snakeHeadY - snackHeadY;

        /**
         * 根据两点位置计算两点的距离
         * @type {number}
         */
        const distance = Math.pow((xdiff * xdiff + ydiff * ydiff), 0.5);
        return distance <= 50
    };

    /**
     * 创建零食
     * 随机取可视区域的点然后重新渲染零食的位置
     */
    createSnack = () => {
        const x = Math.floor(Math.random() * this.maxX);
        const y = Math.floor(Math.random() * this.maxY);
        const color = this.colors[Math.floor(Math.random() * this.colors.length)];

        this.setState({
            snack: {x, y, color}
        })
    };

    /**
     * 生成 Unique Key
     * @returns {string}
     */
    generateUUID = () => {
        let d = new Date().getTime();
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            let r = (d + Math.random() * 16) % 16 || 0;
            d = Math.floor(d / 16);
            return (c === 'x' ? r : (r & 0x7 || 0x8)).toString(16);
        });
    };

    /**
     * 吃零食动作
     */
    snakeEat = () => {
        //吃完零食后再创建另一个零食
        this.addSnakeNode(this.createSnack);
    };

    /**
     * 吃完零食后在蛇身上加一节
     * @param cb
     */
    addSnakeNode = (cb) => {
        const newNode = Object.assign({}, this.state.snack);
        const snakeNodesCopy = this.state.snakeNodes.slice();
        snakeNodesCopy.push(newNode);
        this.setState({snakeNodes: snakeNodesCopy, score: this.state.score + 5}, () => {
            cb();
        })
    };


    /**
     * 监听鼠标移动事件
     * @param e
     */
    mouseMove = (e) => {
        const left = e.clientX;
        const top = e.clientY;

        this.setState({
            left, top,
        }, () => {
            if (this.collisionChecking()) {
                this.snakeEat();
            } else {
                this.rebuildSnakeNodeArray();
            }
        });
    };

    /**
     * 矫正蛇身每一节的位置
     */
    rebuildSnakeNodeArray = () => {
        const result = this.state.snakeNodes.map((node, index) => {
            if (index === 0) {   //蛇头的位置始终要和鼠标相同
                node = {
                    left: this.state.left,
                    top: this.state.top,
                    key: new Date().getTime() + Math.random() + '',
                    color: '#EE30A7'
                };
                return node;
            } else {
                node = this.state.snakeNodes[index - 1];  //后一段蛇身需要移动到前一段蛇身的位置，因此将前一段蛇身的位置赋值给后一段蛇身
                return node;
            }
        }, this);
        this.setState({
            snakeNodes: result
        });
    };

    /**
     * 生成 TransitionMotion 所需要的styles数据
     * @returns {{key: string, style: {left: *, top: *, color: string}}[]}
     */
    getStyles = () => {
        return this.state.snakeNodes.map(node => ({
            key: this.generateUUID(),
            style: {left: spring(node.left, presets.gentle), top: spring(node.top, presets.gentle), color: node.color}
        }))
    };

    render() {
        return (
            <div className={Style["snake-container"]} onMouseMove={this.mouseMove}>
                <p>得分: {this.state.score}</p>
                <TransitionMotion
                    styles={this.getStyles()}
                >
                    {
                        interpolatedStyles => (
                            <div>
                                {
                                    interpolatedStyles.map(({key, style}) => (
                                        <div key={key} className={Style["snake-node"]}
                                             style={{
                                                 top: `${style.top - 25}px`,
                                                 left: `${style.left - 25}px`,
                                                 background: `${style.color}`,
                                             }}>
                                        </div>
                                    ))
                                }
                            </div>
                        )
                    }
                </TransitionMotion>
                <Snack {...this.state.snack}/>
            </div>
        )
    }
}

/**
 * 零食的实体类
 */
class Snack extends Component {
    constructor(props) {
        super(props);
        this.state = {
            x: props.x,
            y: props.y,
            color: '#00BFFF'
        }
    }

    componentWillReceiveProps(nextProps) {
        this.setState({
            x: nextProps.x - 25,
            y: nextProps.y - 25,
            color: nextProps.color
        })
    }

    render() {
        return (
            <div className={Style["snack-item"]}
                 style={{top: `${this.state.y}px`, left: `${this.state.x}px`, background: this.state.color}}>
            </div>
        )
    }
}

export default Snake;